(ns MyBot
  (:require
   [game :refer [single-dimension-distance frontier board-half board-rows board-cols]]
   [io]
   [utils :refer :all]
   [kdtree :as kd :refer [set-dist-func!]])  
  (:gen-class))

(def bot-name "ClCljBot")
(def preferred-directions #{:north :west})
(defrecord SimpleLocation [^int x ^int y])

#_(defmacro time-it [name & body]
  `(let [start# (now)]
     (try
       ~@body
       (catch Throwable e#
         (log e#))
       (finally
         (let [time# (- (now) start#)]
           (log-csv (count @frontier) ";" @n-mysites ";" time#)
           #_(when (> time# 500)
               (log "Call " ~name "time:" time# "frontier-n:" (count @frontier))))))))

(defmacro time-it [name & body]
  `(try
     ~@body
     (catch Throwable e#
       (log e#))))

(defn- offset-to-direction [offset [pos-d neg-d]]
  (let [abs-offset (if (pos? offset) offset (unchecked-negate offset))
        half @board-half]
    (cond
      (or
       (and (pos? offset) (< offset half))
       (and (neg? offset) (> abs-offset half))) pos-d
      (or
       (and (pos? offset) (> offset half))
       (and (neg? offset) (< abs-offset half))) neg-d)))

(defn direction-of [game-map from-site to-site]
  (if-not (:x to-site)
    []
    (let [move-x (offset-to-direction (- (:x to-site) (:x from-site)) [:east :west])
          move-y (offset-to-direction (- (:y to-site) (:y from-site)) [:south :north ])]
      (remove nil? [move-x move-y]))))

(defn friendly-neighbor? [game-map mysite direction]
  (= (:owner (game/adjacent-site game-map mysite direction))
     (:owner mysite)))

(defn site->vec [{:keys [x y]}] [x y])
(defn vec->site [[x y]] (SimpleLocation. x y))

(defn remove-frontier-site [{:keys [x y]}]
  (swap! frontier kd/delete [x y]))

(defn- distance-func [[x1 y1] [x2 y2]]
  (let [d (game/distance (SimpleLocation. x1 y1) (SimpleLocation. x2 y2))]
    (log "distance" d [x1 y1] [x2 y2])
    d))

(defn add-frontier-site [{:keys [x y] :as site}]
  (swap! frontier kd/insert [x y]))

(defn frontier-direction-by-min-distance [game-map mysite]
  (when-let [{:keys [loc] :as t}
             (reduce (fn [{:keys [dist] :as near-site} {:keys [x y] :as target}]
                       (let [dist-curr (game/distance mysite target)]
                         (if (or (not dist) (< dist-curr dist))
                           {:dist dist-curr :loc target}
                           near-site)))
                     {:dist (Integer/MAX_VALUE)}
                     @frontier)]
    (direction-of game-map mysite loc)))

(defn make-move [game-map]
  (fn [{:keys [owner production strength] :as mysite}]
    (let [all-friendly?
          (every? (partial friendly-neighbor? game-map mysite) game/cardinal-directions)

          _ (if all-friendly?
              (remove-frontier-site mysite)
              (add-frontier-site mysite))

          frontier-directions
          (when (and all-friendly? (> strength (* production 5)))
            ;; (frontier-direction-by-min-distance game-map mysite)
            (let [r (-> @frontier
                        (kd/nearest-neighbor [(:x mysite) (:y mysite)]))]
              (log "neighbor distance: " (:dist-squared r) [(:x mysite) (:y mysite)] (:point r))
              (direction-of game-map mysite (-> r :point vec->site))))
          
          kill-enmey-direction
          (when-not all-friendly?
            (some (fn [direction]
                    (let [{:keys [x y] :as neighbor} (game/adjacent-site game-map mysite direction)]
                      (when (and (not= owner (:owner neighbor))
                                 (> strength (:strength neighbor)))
                        direction)))
                  game/cardinal-directions))]
      [mysite (or kill-enmey-direction
                  (when (seq frontier-directions)
                    (rand-nth frontier-directions))
                  :still)])))

(defn- game-map-to-mysites [game-map my-id]
  (->> game-map flatten (filter #(= my-id (:owner %)))))

(defn -main []
  (let [{:keys [my-id productions width height game-map] :as init-data} (io/get-init!)]
    (log "Start testing")
    (reset! board-rows (count game-map))
    (reset! board-cols (count (first game-map)))
    (reset! board-half (/ (+ @board-rows @board-cols) 2))
    (reset! frontier (kd/build-tree (map site->vec (game-map-to-mysites game-map my-id))))
    (log "board info:" @board-rows @board-cols @board-half)
    (set-dist-func! distance-func)
    
    (println bot-name)
    ;; (spit (clojure.java.io/file "init-data.edn") init-data)
    ;; (def idata (miner.tagged/read-string (slurp "init-data.edn")))
    ;; (->> (flatten (:game-map idata)) (filter #(= 2 (-> % :value :owner))))

    (doseq [turn (range)]
      (let [game-map (io/create-game-map width height productions (io/read-ints!))
            mysites (game-map-to-mysites game-map my-id)
            mover (make-move game-map)]
        (time-it
         "send-moves"
         (io/send-moves! (map mover mysites)))))))
